using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using System.Text.RegularExpressions;

namespace TacticalGridWars
{
    /// <summary>
    /// Main game class
    /// </summary>
    public class TacticalGridWars : Microsoft.Xna.Framework.Game
    {

        #region MemberData
        private enum GameState { Menu, Game, Load, EndScreen }
        private GameState gameState = GameState.Menu;
        private GraphicsDeviceManager graphics;
        private SpriteBatch spriteBatch;
        private GameGrid gameGrid;
        private Bullets bullets;
        private Team team1;
        private Team team2;
        private Team curTeam;
        private Team otherTeam;
        private Random rng = new Random((int)DateTime.Now.Ticks & 0x0000FFFF);
        private string selectedLevel = "Level1";
        private int turn = 0;

        private Vector2 menuPos;
        private Texture2D buttonTexture;
        private List<Rectangle> menuHitboxes = new List<Rectangle>();
        private string input = "";
        private Keys[] oldKeys;

        private SpriteFont font;
        private string gameText = "";

        private enum TurnState { None, Move, Attack }
        private TurnState turnState = TurnState.None;

        private MouseState oldMouse;
        private KeyboardState oldKeyboard; 
        #endregion

        public TacticalGridWars()
        {
            graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";
            graphics.PreferredBackBufferHeight = 110;
            graphics.PreferredBackBufferWidth = 210;
        }

        /// <summary>
        /// Allows the game to perform any initialization it needs to before starting to run.
        /// This is where it can query for any required services and load any non-graphic
        /// related content.  Calling base.Initialize will enumerate through any components
        /// and initialize them as well.
        /// </summary>
        protected override void Initialize()
        {
            gameGrid = new GameGrid(this);
            team1 = new Team(this, Color.Green,"Green Team");
            team2 = new Team(this, Color.Purple, "Purple Team");
            bullets = new Bullets(this);
            this.Components.Add(bullets);
            this.Components.Add(gameGrid);
            this.Components.Add(team1);
            this.Components.Add(team2);
            this.IsMouseVisible = true;
            menuPos = new Vector2(5, 5);
            menuHitboxes.Add(new Rectangle(0, 0, 100, 50));
            menuHitboxes.Add(new Rectangle(100, 0, 100, 50));
            menuHitboxes.Add(new Rectangle(0, 50, 100, 50));
            menuHitboxes.Add(new Rectangle(100, 50, 100, 50));
            base.Initialize();
        }

        /// <summary>
        /// LoadContent will be called once per game and is the place to load
        /// all of your content.
        /// </summary>
        protected override void LoadContent()
        {
            buttonTexture = this.Content.Load<Texture2D>("buttonborder");
            font = this.Content.Load<SpriteFont>("font");
            // Create a new SpriteBatch, which can be used to draw textures.
            spriteBatch = new SpriteBatch(GraphicsDevice);
        }

        /// <summary>
        /// Allows the game to run logic such as updating the world,
        /// checking for collisions, gathering input, and playing audio.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Update(GameTime gameTime)
        {
            GetCurrentTeam();
            KeyboardState keyboard = Keyboard.GetState();
            MouseState mouse = Mouse.GetState();

            switch (gameState)
            {
                //#################//
                //  Menu controls  //
                //#################//
                case GameState.Menu:
                    if (mouse.LeftButton == ButtonState.Pressed && oldMouse.LeftButton == ButtonState.Released)
                    {
                        if (menuHitboxes[0].Contains(mouse.X, mouse.Y))
                        {
                            if (curTeam.TeamList.Count == 0)
                            {
                                LoadLevel(selectedLevel);
                            }
                            gameState = GameState.Game;
                        }
                        else if (menuHitboxes[1].Contains(mouse.X, mouse.Y))
                        {
                            LoadLevel(selectedLevel);
                            gameState = GameState.Game;
                        }
                        else if (menuHitboxes[2].Contains(mouse.X, mouse.Y))
                        {
                            input = "";
                            gameState = GameState.Load;
                        }
                        else if (menuHitboxes[3].Contains(mouse.X, mouse.Y))
                        {
                            this.Exit();
                        }
                    }
                    break; //menu controls

                //#################//
                //  Game controls  //
                //#################//
                case GameState.Game:
                    if (keyboard.IsKeyDown(Keys.M) && oldKeyboard.IsKeyUp(Keys.M) && curTeam.SelectedSoldier.Moves > 0)
                    {
                        turnState = TurnState.Move;
                        gameGrid.Grid.UpdateReachable(curTeam.SelectedSoldier);
                        ClearAttackable();
                    }
                    else if (keyboard.IsKeyDown(Keys.A) && oldKeyboard.IsKeyUp(Keys.A) && curTeam.SelectedSoldier.Moves > 0)
                    {
                        turnState = TurnState.Attack;
                        GetAttackable();
                    }
                    else if (keyboard.IsKeyDown(Keys.W) && oldKeyboard.IsKeyUp(Keys.W))
                    {
                        turnState = TurnState.None;
                        curTeam.SelectedSoldier.Moves = 0;
                        curTeam.SelectedSoldier.Selected = false;
                    }
                    else if (keyboard.IsKeyDown(Keys.C) && oldKeyboard.IsKeyUp(Keys.C))
                    {
                        turnState = TurnState.None;
                        gameGrid.Grid.ClearReachable();
                        ClearAttackable();
                    }
                    else if (keyboard.IsKeyDown(Keys.Escape) && oldKeyboard.IsKeyUp(Keys.Escape))
                    {
                        gameState = GameState.Menu;
                    }
            
                    //Mouse commands
                    else if (mouse.LeftButton == ButtonState.Pressed && oldMouse.LeftButton == ButtonState.Released)
                    {
                        int x = mouse.X / 50;
                        int y = mouse.Y / 50;

                        if (x >= 0 && x <= gameGrid.Grid.Columns - 1 && y >= 0 && y <= gameGrid.Grid.Rows - 1)
                        {
                            switch (turnState)
                            {
                                case TurnState.None:
                                    if (gameGrid.Grid.Level[y, x].Occupied)
                                    {
                                        curTeam.DoSoldierSelection(x, y);
                                    }
                                    break;
                                case TurnState.Move:
                                    if (gameGrid.Grid.Level[y, x].Reachable && gameGrid.Grid.Level[y, x].Passable)
                                    {
                                        if (gameGrid.Grid.Level[y, x].WalkReachable && curTeam.SelectedSoldier.Moves > 0)
                                        {
                                            MoveSoldier(ref x, ref y);
                                        }
                                        else if (gameGrid.Grid.Level[y, x].SprintReachable && curTeam.SelectedSoldier.Moves > 1)
                                        {
                                            MoveSoldier(ref x, ref y);
                                        }
                                    }
                                    break;
                                case TurnState.Attack:
                                    if (gameGrid.Grid.Level[y, x].Occupied)
                                    {
                                        Soldier target = otherTeam.CheckAttackableSoldiers(x, y);
                                        if (target != null)
                                        {
                                            double rand = rng.NextDouble();
                                            gameText = "Hitchance: " + target.HitChance.ToString() + "\nRolled for: " + rand.ToString("0.00");
                                            bullets.Fire(curTeam.SelectedSoldier.XPos * 50 + 25, curTeam.SelectedSoldier.YPos * 50 + 25,
                                                x * 50 + 25, y * 50 + 25, target, rand <= target.HitChance);
                                            if (rand <= target.HitChance)
                                            {
                                                curTeam.Hits++;
                                            }
                                            curTeam.ShotsFired++;
                                            curTeam.SelectedSoldier.Moves = 0;
                                            ClearAttackable();
                                            turnState = TurnState.None;
                                        }
                                    }
                                    break;
                                default:
                                    break;
                            }
                        }
                    }                    

                    if (curTeam.NoMovesLeft)
                    {
                        curTeam.ResetTeam();
                        turn = ++turn % 2;
                    }
                    if (otherTeam.AllDead)
                    {
                        gameState = GameState.EndScreen;
                    }
                    break;//game controls

                //#################//
                //  Load controls  //
                //#################//
                case GameState.Load:
                    Keys[] pressed = keyboard.GetPressedKeys();
                    Regex rgx = new Regex(@"^[A-Z0-9]$");
                    if (keyboard.IsKeyDown(Keys.Enter) && oldKeyboard.IsKeyUp(Keys.Enter))
                    {
                        selectedLevel = input;
                        LoadLevel(selectedLevel);
                        gameState = GameState.Game;
                    }
                    else if (keyboard.IsKeyDown(Keys.Escape) && oldKeyboard.IsKeyUp(Keys.Escape))
                    {
                        gameState = GameState.Game;
                    }
                    else if (keyboard.IsKeyDown(Keys.Back) && oldKeyboard.IsKeyUp(Keys.Back))
                    {
                        try
                        {
                            input = input.Remove(input.Length - 1);
                        }
                        catch { }
                    }
                    else if (pressed.Length != 0 && KeysComp(pressed, oldKeys))
                    {
                        char text = pressed[0].ToString()[pressed[0].ToString().Length - 1];
                        if (rgx.IsMatch(text.ToString()))
                        {
                            if (keyboard.IsKeyDown(Keys.LeftShift) || keyboard.IsKeyDown(Keys.RightShift))
                            {                                
                                input += text;
                            }
                            else
                            {
                                input += text.ToString().ToLower();
                            }
                        }
                    }
                    oldKeys = pressed;
                    break;//load controls

                //#################//
                //  End controls  //
                //#################//
                case GameState.EndScreen:
                    if (keyboard.IsKeyDown(Keys.Escape) && oldKeyboard.IsKeyUp(Keys.Escape))
                    {
                        gameState = GameState.Menu;
                    }
                    break;//end controls
                default:
                    break;
            }

            base.Update(gameTime);

            oldMouse = mouse;
            oldKeyboard = keyboard;
        }

        /// <summary>
        /// This is called when the game should draw itself.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Draw(GameTime gameTime)
        {
            GraphicsDevice.Clear(Color.Black);
            spriteBatch.Begin();
            switch (gameState)
            {
                case GameState.Menu:
                    spriteBatch.Draw(buttonTexture, menuPos, Color.Green);
                    spriteBatch.DrawString(font, "Start", menuPos + new Vector2(20, 10), Color.Green);
                    spriteBatch.Draw(buttonTexture, menuPos + new Vector2(100,0), Color.Green);
                    spriteBatch.DrawString(font, "Reset", menuPos + new Vector2(120, 10), Color.Green);
                    spriteBatch.Draw(buttonTexture, menuPos + new Vector2(0,50), Color.Green);
                    spriteBatch.DrawString(font, "Load", menuPos + new Vector2(20, 60), Color.Green);
                    spriteBatch.Draw(buttonTexture, menuPos + new Vector2(100,50), Color.Green);
                    spriteBatch.DrawString(font, "Exit", menuPos + new Vector2(120, 60), Color.Green);
                    break;
                case GameState.Game:
                    spriteBatch.DrawString(font, gameText, new Vector2(graphics.PreferredBackBufferWidth - 250, 75), Color.Cyan * 0.75f);
                    spriteBatch.DrawString(font, curTeam.Name + "'s turn\n" + curTeam.TotalMoves + " moves left", new Vector2(graphics.PreferredBackBufferWidth - 250, 0), curTeam.TeamColor);
                    if (curTeam.SelectedSoldier.Selected)
                    {
                        spriteBatch.DrawString(font, "Moves: " + curTeam.SelectedSoldier.Moves, new Vector2(graphics.PreferredBackBufferWidth - 250, 50), curTeam.TeamColor);
                    }
                    base.Draw(gameTime);
                    break;
                case GameState.Load:
                    spriteBatch.DrawString(font, "Levelname:", new Vector2(10, 10), Color.Green);
                    spriteBatch.DrawString(font, input + '_', new Vector2(10, 30), Color.LightGreen);
                    break;
                case GameState.EndScreen:
                    spriteBatch.DrawString(font, curTeam.Name + " won the game!", new Vector2(10, 10), curTeam.TeamColor);
                    spriteBatch.DrawString(font, "Shots fired: " + curTeam.ShotsFired, new Vector2(10, 30), curTeam.TeamColor);
                    spriteBatch.DrawString(font, "Hits: " + curTeam.Hits, new Vector2(10, 50), curTeam.TeamColor);
                    spriteBatch.DrawString(font, "Accuracy:" + curTeam.HitPercent.ToString("F") + "%", new Vector2(10, 70), curTeam.TeamColor);
                    break;
                default:
                    break;
            }
            spriteBatch.End();
        }

        private void GetCurrentTeam()
        {
            if (turn == 0)
            {
                curTeam = team1;
                otherTeam = team2;
            }
            else
            {
                curTeam = team2;
                otherTeam = team1;
            }
            curTeam.Active = true;
        }

        private void MoveSoldier(ref int x, ref int y)
        {
            gameGrid.Grid.Level[curTeam.SelectedSoldier.YPos, curTeam.SelectedSoldier.XPos].Occupied = false;
            curTeam.SelectedSoldier.SetPosition(x, y);
            if (gameGrid.Grid.Level[y, x].WalkReachable)
            {
                curTeam.SelectedSoldier.Moves -= 1;
            }
            else
            {
                curTeam.SelectedSoldier.Moves -= 2;
            }
            if (curTeam.SelectedSoldier.Moves <= 0)
            {
                curTeam.SelectedSoldier.Selected = false;
            }
            gameGrid.Grid.ClearReachable();
            gameGrid.Grid.Level[y, x].Occupied = true;
            turnState = TurnState.None;
        }

        private void GetAttackable()
        {
            foreach (Soldier soldier in otherTeam.TeamList)
            {                
                soldier.HitChance = gameGrid.Grid.GetHitChance(curTeam.SelectedSoldier.XPos * 50 + 25, curTeam.SelectedSoldier.YPos * 50 + 25,
                soldier.XPos * 50 + 25, soldier.YPos * 50 + 25);
                if (soldier.HitChance > 0)
                {
                    soldier.Attackable = true;
                }
            }
        }

        private void ClearAttackable()
        {
            foreach (Soldier soldier in otherTeam.TeamList)
            {
                soldier.Attackable = false;
                soldier.HitChance = 0;
            }
        }

        private void LoadLevel(string levelname)
        {
            turn = 0;
            gameGrid.Grid.LoadLevel(levelname);
            team1.Init(gameGrid.Grid.Spawns.FindAll(x => x.Team == 1), gameGrid.Grid);
            team2.Init(gameGrid.Grid.Spawns.FindAll(x => x.Team == 2), gameGrid.Grid);
            graphics.PreferredBackBufferHeight = 50 * gameGrid.Grid.Rows;
            graphics.PreferredBackBufferWidth = 50 * gameGrid.Grid.Columns + 300;
            graphics.ApplyChanges();
        }

        private bool KeysComp(Keys[] c1, Keys[] c2)
        {
            for (int i = 0; i < c1.Length; i++) 
            {
                try
                {
                    if (c1[i] != c2[i])
                    {
                        return true;
                    }
                }
                catch
                {
                    return true;
                }
            }
            return false;
        }
    }
}
